/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.cdiggins.com
*/

#ifndef CVML_EXPR_MAKER_HPP
#define CVML_EXPR_MAKER_HPP

struct ExprMaker
{
	struct Expr
	{
		virtual std::string GetName() {
			return "???";
		}
		virtual std::string GetType() {
			return "object";
		}
	};

	struct LitExpr : Expr
	{
		std::string name;
		std::string type;

		LitExpr(const std::string& _name, const std::string& _type = "Object")
			: name(_name), type(_type)
		{ }

		std::string GetName() {
			return name;
		}

		std::string GetType() {
			return type;
		}
	};

	struct UnaryExpr : Expr
	{
		std::string op;
		Expr* expr;

		UnaryExpr(const std::string& _op, Expr* x)
			: op(_op), expr(x)
		{ }

		std::string GetName() {
			return std::string("(") + op + " " + expr->GetName() + ")";
		}
	};


	struct BinaryExpr : Expr
	{
		std::string op;
		Expr* expr1;
		Expr* expr2; 

		BinaryExpr(const std::string& _op, Expr* x1, Expr* x2)
			: op(_op), expr1(x1), expr2(x2) 
		{ }

		std::string GetName() {
			return std::string("(") + op + " " + expr1->GetName() + " " + expr2->GetName() + ")";
		}
	};

	struct TertiaryExpr : Expr
	{
		std::string op;
		Expr* expr1;
		Expr* expr2; 
		Expr* expr3;

		TertiaryExpr(const std::string& _op, Expr* x1, Expr* x2, Expr* x3)
			: op(_op), expr1(x1), expr2(x2), expr3(x3) 
		{ }

		std::string GetName() {
			return std::string("(") + op + " " + expr1->GetName() + " " + expr2->GetName() + " " + expr3->GetName() + ")";
		}
	};

	std::vector<Expr*> exprs;
	Stack<Expr*> stk;
	static const bool logging = true;

	void PushLitExpr()
	{
		Expr* x = new LitExpr(std::string("x") + ToStr(exprs.size()));
		exprs.push_back(x);
		stk.Push(x);
	}

	void PushUnaryExpr(const std::string& op)
	{
		Expr* tmp = stk.Pull();
		Expr* x = new UnaryExpr(op, tmp);
		exprs.push_back(x);
		stk.Push(x);
	}

	void PushBinaryExpr(const std::string& op)
	{
		Expr* tmp1 = stk.Pull();
		Expr* tmp2 = stk.Pull();
		Expr* x = new BinaryExpr(op, tmp2, tmp1);
		exprs.push_back(x);
		stk.Push(x);
	}

	void PushTertiaryExpr(const std::string& op)
	{
		Expr* tmp1 = stk.Pull();
		Expr* tmp2 = stk.Pull();
		Expr* tmp3 = stk.Pull();
		Expr* x = new TertiaryExpr(op, tmp3, tmp2, tmp1);
		exprs.push_back(x);
		stk.Push(x);
	}

	ExprMaker() {
	}

	void PrintStack() {
		printf("Stack: ");
		int n = stk.Size();
		for (int i=0; i < n; ++i) {
			Expr* x = stk.Below(n - i - 1);
			printf("%s ", x->GetName().c_str());
		}
		printf("\n");
	}

	void Parse(Subroutine* sub) {
		Parse(sub->first);
	}

	void Pop() {
		if (stk.IsEmpty())
			return;
		stk.Pop();
	}

	Expr*& Top() {
		if (stk.IsEmpty()) {
			PushLitExpr();
		}
		return stk.Top();
	}

	Expr* Pull() { 
		Expr* r = Top();
		Pop();
		return r;
	}

	Expr*& Below(int n) {
		if (n >= stk.Size())
		{
			int d = n - stk.Size();
			Stack<Expr*> tmp(stk);
			stk.Clear();

			// <= is correct
			for (int i=0; i <= d; ++i) {
				PushLitExpr();
			}
			stk.PushAll(tmp);
		}
		Expr*& r = stk.Below(n);
		return r;
	}

	void Push(Expr* x) {
		stk.Push(x);
	}

	void Swap() {
		Expr* x = Pull();
		Expr* y = Pull();
		stk.Push(x);
		stk.Push(y);
	}

	void Parse(Instruction* begin, Instruction* end = NULL) {
		Expr* tmp;
		for (Instruction* i = begin; i != end; i = i->next) {
			if (logging) {
				PrintStack();
				printf("Instruction %s\n", i->ToString().c_str());
			}

			int operand = i->operand;
			switch (i->opcode)
			{
            case(op_noop) : 
                break;

            // push values onto the stack
			case(op_push_int) : 
				PushLitExpr();
				break;
            case(op_push_char) : 
				PushLitExpr();
				break;
            case(op_push_byte) : 
				PushLitExpr();
                break;
            case(op_push_sub) : 
				PushLitExpr();
                break;
            case(op_push_neg1) : 
				PushLitExpr();
                break;
            case(op_push0) : 
				PushLitExpr();
                break;
            case(op_push1) : 
				PushLitExpr();
                break;
            case(op_push2) : 
				PushLitExpr();
                break;
            case(op_push_true) : 
				PushLitExpr();
                break;
            case(op_push_false) : 
				PushLitExpr();
                break;
            case(op_push_string) : 
				PushLitExpr();
                break;
			case(op_push_short) : 
				PushLitExpr();
				break;

            // stack manipulation
            case(op_pop3) :
                Pop();
            case(op_pop2) : 
                Pop();
            case(op_pop) : 
                Pop();
                break;
            case(op_popd) :
                Swap();
                Pop();
                break;
            case (op_dup) :
                Push(Top());
                break;
            case (op_dupd) :
                tmp = Pull();
                Push(Top());
                Push(tmp);
                break;
            case (op_dup2) :
                Push(Below(1));
                Push(Below(1));
                break;
            case (op_dup3) :
                Push(Below(2));
                Push(Below(2));
                Push(Below(2));
                break;
            case (op_swap) :
                Swap();
                break;
            case (op_swapd) :
                tmp = Pull();
                Swap();
                Push(tmp);
                break;
            case (op_swap1):
				tmp = Top();
				Top() = Below(1);
				Below(1) = tmp;
				break;
            case (op_swap2):
				tmp = Top();
				Top() = Below(2);
				Below(2) = tmp;
				break;
            case (op_swap3):
				tmp = Top();
				Top() = Below(3);
				Below(3) = tmp;
				break;
            case (op_swapN):
				tmp = Top();
				Top() = Below(operand);
				Below(operand) = tmp;
				break;

            // get and set
            case (op_getN):
                Push(Below(operand));
                break;
            case (op_get1):
                Push(Below(1));
                break;
            case (op_get2):
                Push(Below(2));
                break;
            case (op_get3):
                Push(Below(3));
                break;
            case (op_get4):
                Push(Below(4));
                break;
            case (op_get5):
                Push(Below(5));
                break;
            case (op_get6):
                Push(Below(6));
				break;
            case (op_get7):
                Push(Below(7));
                break;
            case (op_get8):
                Push(Below(8));
                break;
            case (op_setN):
                Below(operand) = Top();
                Pop();
                break;
            case (op_set1):
                Swap();
                Pop();
                break;
            case (op_set2):
                Push(Below(2));
                Pop();
                break;
            case (op_set3):
                Push(Below(3));
                Pop();
                break;
            case (op_set4):
                Push(Below(4));
                Pop();
                break;
            case (op_set5):
                Push(Below(5));
                Pop();
                break;
            case (op_set6):
                Push(Below(6));
                Pop();
                break;
            case (op_set7):
                Push(Below(7));
                Pop();
                break;
            case (op_set8):
                Push(Below(8));
                Pop();
                break;

            // comparisons
            case (op_lt):
            case (op_lt_int):
            case (op_lt_flt):
                PushBinaryExpr("lt");
                break;
            case (op_gt):
            case (op_gt_int):
            case (op_gt_flt):
                PushBinaryExpr("gt");
                break;
            case (op_lteq):
            case (op_lteq_int):
            case (op_lteq_flt):
                PushBinaryExpr("lteq");
                break;
            case (op_gteq):
            case (op_gteq_int):
            case (op_gteq_flt):
                PushBinaryExpr("gteq");
                break;
            case (op_eq):
            case (op_eq_int):
            case (op_eq_flt):
                PushBinaryExpr("eq");
                break;
            case (op_neq):
            case (op_neq_int):
            case (op_neq_flt):
                PushBinaryExpr("neq");
                break;

            // artithmetic
            case (op_add):
            case (op_add_int):
            case (op_add_flt):
                PushBinaryExpr("add");
                break;
            case (op_sub):
            case (op_sub_int):
            case (op_sub_flt):
                PushBinaryExpr("sub");
                break;
            case (op_mul):
            case (op_mul_int):
            case (op_mul_flt):
                PushBinaryExpr("mul");
                break;
            case (op_div):
            case (op_div_int):
            case (op_div_flt):
                PushBinaryExpr("div");
                break;
            case (op_mod_int):
                PushBinaryExpr("mod");
                break;
            case (op_neg):
            case (op_neg_flt):
            case (op_neg_int):
                PushUnaryExpr("neg");
                break;
            case (op_inc):
                PushUnaryExpr("inc");
                break;
            case (op_dec):
                PushUnaryExpr("dec");
                break;

            // boolean operations
            case (op_not):
                PushUnaryExpr("not");
                break;
            case (op_or):
                PushBinaryExpr("not");
                break;
            case (op_and):
                PushUnaryExpr("and");
                break;
            case (op_xor):
                PushUnaryExpr("xor");
                break;

            // higher order functions
            case (op_ret):
				// Terminates the function
                return;
            case (op_apply):
				// TODO:
				// Note: we need the type of the functiOon!
				Pop();
				break;
            case (op_tail_apply):
				// TODO:
				Error("Not implemented yet");
                break;
			case (op_call):
                Parse(i->GetSub());
                break;
            case (op_tail_call):
                Parse(i->GetSub());
                return;
            case (op_dip):
				Error("unimplemented");
				break;

            // control flow (loops and conditional)
            case (op_if):
				Pop();
				Pop();
				Pop();
				break;
            case (op_while):
				Pop();
				Pop();
                break;
			case (op_foreach_index):
				Pop();
				Pop();
				break;

            // objects
            case (op_get_slot) :
                Error("unimplemented");
                break;
            case (op_set_slot) :
                Error("unimplemented");
                break;
            case (op_new) : 
                Error("unimplemented");
                break;        
            case (op_null) :
                Error("unimplemented");
                break;

            // arrays and lists
            case (op_new_array) :
                Error("unimplemented");
                break;
            case (op_count):
                PushUnaryExpr("count");
                break;
            case (op_get_at):
				PushBinaryExpr("get_at");
				break;
            case (op_set_at):
                PushTertiaryExpr("set_at");
                break;
            case (op_fold):
                Error("unimplemented");
                break;
            case (op_map):
                Error("unimplemented");
                break;
            case (op_filter):
                Error("unimplemented");
                break;
            case (op_filter_map):
                Error("unimplemented");
                break;
            case (op_range):
                Error("unimplemented");
                break;
            case (op_empty):
                Error("unimplemented");
                break;
            case (op_nil):
                Error("unimplemented");
                break;
            case (op_lookup):
                Error("unimplemented");
                break;

            // Indicates that this is an extended byte-code set
            case (op_extended):
                Error("unimplemented");
                break;
            
			default:
                Error("unrecognized op-code");
			}
		}
	}

	~ExprMaker() {		
		DeletePointers(exprs);
	}

};

#endif

